package com.lx.wrapper;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import com.lx.common.utils.CommonClassUtil;
import com.lx.common.utils.SerializedLambdaUtil;
import com.lx.constant.ano.FieldFunction;
import com.lx.constant.consts.CommonConstant;
import com.lx.constant.enums.ConditionKeyWordEnum;
import com.lx.constant.enums.SegmentEnum;
import com.lx.constant.execption.LambdaCheckException;
import com.lx.constant.execption.LambdaIllegalParamException;
import com.lx.constant.segment.Segment;
import com.lx.constant.segment.common.ConditionSegment;
import com.lx.constant.segment.common.QlCompleteSegment;
import com.lx.constant.segment.common.QlFragmentSegment;
import com.lx.inter.query.QueryHandlerFunction;
import org.hibernate.Session;
import org.hibernate.query.Query;

import javax.persistence.*;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基础Wrapper H/SQL语句 抽象执行类
 * <p>
 * 用途：
 * 1.根据已有的各类组合语句生成HQL语句
 * 2.根据已有的各类组合语句生成SQL语句
 * 3.提供公共执行SQL封装能力
 *
 * @author lx
 * @create 2022/11/15 18:38
 **/
public abstract class AbstractWrapper<MainEntity, ChildWrapper extends AbstractWrapper<MainEntity, ChildWrapper>> {

    /**
     * 日志输出
     */
    protected final Log log = LogFactory.get(this.getClass());

    public AbstractWrapper(Class<MainEntity> entityClass) {
        this.entityClass = entityClass;
    }

    /**
     * 主实体类类型
     */
    private volatile Class<MainEntity> entityClass;

    /**
     * 实体类表名后缀
     */
    private volatile String tableSuffix;

    /**
     * 主实体类表名是否开启驼峰命名法解析,默认为true
     */
    protected Boolean openHump = true;

    /**
     * 用户自定义Query处理执行器
     */
    protected QueryHandlerFunction<ChildWrapper> userQueryHandlerFunc;

    /**
     * 实体类别名Map,根据实体类型获取实体表别名
     * 注意:
     * 1.当存在一个实体类对应多张表时,需要补全表名后缀,通过表明后缀进行区分
     */
    private final Map<Class, String> ENTITY_TABLE_ALIAS_NAME_MAP = new ConcurrentHashMap<>(8);

    /**
     * 语句组件缓存
     */
    private final Map<SegmentEnum, List<Segment>> SEGMENT_CACHE = new HashMap<>();

    public ChildWrapper setOpenHump(boolean openHump) {
        this.openHump = openHump;
        return (ChildWrapper) this;
    }

    public ChildWrapper setEntityClass(Class<MainEntity> entityClass) {
        this.entityClass = entityClass;
        return (ChildWrapper) this;
    }

    public Class<MainEntity> getEntityClass() {
        return entityClass;
    }

    public QueryHandlerFunction<ChildWrapper> getUserQueryHandlerFunc() {
        return userQueryHandlerFunc;
    }

    /**
     * 设置主实体类型表别名,如果别名已经生成,则无法设置
     *
     * @param tableAlias
     */
    public synchronized final ChildWrapper setTableAlias(String tableAlias) {
        return setTableAlias(checkAndGetValidEntityClass(), tableAlias);
    }

    /**
     * 根据传入的实体类型设置表别名,如果别名已经生成,则无法设置
     *
     * @param tableAlias
     */
    public synchronized final ChildWrapper setTableAlias(Class otherEntityClass, String tableAlias) {
        if (otherEntityClass == null) {
            throw new LambdaIllegalParamException("设置Wrapper中其他实体类型的别名时,实体类型不能为空!");
        }
        ENTITY_TABLE_ALIAS_NAME_MAP.putIfAbsent(otherEntityClass, tableAlias);
        return (ChildWrapper) this;
    }

    /**
     * 设置实体类对应的表名后缀
     *
     * @param tableSuffix
     */
    public ChildWrapper setTableSuffix(String tableSuffix) {
        this.tableSuffix = tableSuffix;
        return (ChildWrapper) this;
    }

    /**
     * 获取实体类对应的表名后缀
     *
     * @return
     */
    public String getTableSuffix() {
        return this.tableSuffix;
    }

    /**
     * 根据语句类型判断是否存在该语句类型,作用于外界对Wrapper对象可能的推断
     *
     * @param segmentEnum
     * @return
     */
    public boolean existTheSegment(SegmentEnum segmentEnum) {
        return segmentEnum == null ? false : CollectionUtil.isNotEmpty(getSegmentsByEnum(segmentEnum));
    }

    /**
     * 清除该语句枚举下所有的语句拼接列表
     *
     * @param segmentType
     * @return
     */
    public boolean cleanTheSegment(SegmentEnum segmentType) {
        if (segmentType == null) {
            return false;
        }
        SEGMENT_CACHE.remove(segmentType);
        return true;
    }

    /**
     * 获取单个Segment语句列表
     *
     * @param segmentType
     * @param <E>
     * @return
     */
    public final <E extends Segment> List<E> getSegmentsByEnum(SegmentEnum segmentType) {
        Map<SegmentEnum, List<E>> segmentEnumListMap = getSegmentsByEnums(segmentType);
        List<E> es = segmentEnumListMap.get(segmentType);
        return es == null ? Collections.EMPTY_LIST : es;
    }

    /**
     * 获取主实体类型的表表名,如未设置,则生成规则参考下列方法: {@link AbstractWrapper#getTableAliasName(boolean, Class)}
     *
     * @param needPoint
     * @return
     */
    public final String getTableAliasName(boolean needPoint) {
        return getTableAliasName(needPoint, checkAndGetValidEntityClass());
    }

    /**
     * 获取任意实体类型的表别名,如未设置,则按照下方规则生成;
     * 注意:一旦生成,则不会再变
     * 规则:随机取实体类型名称3位字符加1位随机数当作表名
     *
     * @param needPoint 是否需要添加点; 示例：true = a. / false = a
     * @return
     */
    public final String getTableAliasName(boolean needPoint, Class otherEntityClass) {
        if (otherEntityClass == null) {
            return CommonConstant.EMPTY;
        }
        String tableAliasName, pointer = (needPoint ? CommonConstant.POINT : CommonConstant.EMPTY);

        if (StrUtil.isNotEmpty(tableAliasName = ENTITY_TABLE_ALIAS_NAME_MAP.get(otherEntityClass))) {
            return tableAliasName + pointer;
        }

        synchronized (this) {
            if (StrUtil.isNotEmpty(tableAliasName = ENTITY_TABLE_ALIAS_NAME_MAP.get(otherEntityClass))) {
                return tableAliasName + pointer;
            }
            String tableNames = otherEntityClass.getSimpleName();
            int length = tableNames.length();
            Random random = RandomUtil.getRandom(false);

            //写入实体类型
            ENTITY_TABLE_ALIAS_NAME_MAP.put(otherEntityClass, (tableAliasName = new StringBuilder(8)
                    .append(tableNames.charAt(Math.abs(random.nextInt()) % length))
                    .append(tableNames.charAt(Math.abs(random.nextInt()) % length))
                    .append(tableNames.charAt(Math.abs(random.nextInt()) % length))
                    .append(RandomUtil.randomInt(0, 10))
                    .toString()));
            return tableAliasName + pointer;
        }
    }

    /**
     * 根据实体类属性推断和获取表名.
     * 步骤:
     * 1.如实体类存在 {@link Table}注解,则表名取name()
     * 2.如实体类存在 {@link Entity}注解,则表名取name()
     * 3.判断实体类是否需要通过驼峰命名法解析表名
     *
     * @param isHql 是否判断生成表名对应HQL
     * @return
     */
    public String inferAndGetTableName(boolean isHql) {
        Class<MainEntity> entityClass = checkAndGetValidEntityClass();

        if (isHql) {
            //如果是Hql,则只需要返回类名即可.
            return entityClass.getSimpleName();
        }

        String tableName, tableSuffix = this.tableSuffix == null ? CommonConstant.EMPTY : this.tableSuffix;
        Table ta = entityClass.getAnnotation(Table.class);
        if (ta != null && StrUtil.isNotEmpty(tableName = ta.name())) {
            return tableName + tableSuffix;
        }

        Entity ea = entityClass.getAnnotation(Entity.class);
        if (ea != null && StrUtil.isNotEmpty(tableName = ea.name())) {
            return tableName + tableSuffix;
        }
        tableName = entityClass.getSimpleName();

        return (openHump ? CharSequenceUtil.toUnderlineCase(tableName) : tableName) + tableSuffix;
    }

    /**
     * 根据Session创建并获取Hibernate的SQL操作对象
     *
     * @param session
     * @param isHQL
     * @return
     */
    public final Query getQuery(Session session, boolean isHQL) {
        if (session == null) {
            return null;
        }
        String ql = getCompleteQL(isHQL);
        Query query = isHQL ? session.createQuery(ql) : session.createNativeQuery(ql);

        //补充Hibernate参数
        setHibernateParamByQuery(isHQL, query);
        return query;
    }

    /**
     * 根据语句类型添加H/SQL相应的语句字符串
     * 本方法不维护QL类型,仅会拼接到已有的语句.
     *
     * @param segmentType
     * @param fragmentQL
     * @return
     */
    public final ChildWrapper attachFragmentQL(SegmentEnum segmentType, String fragmentQL) {
        if (segmentType == null) {
            throw new LambdaIllegalParamException("添加片段语句时,未指定语句类型!");
        }
        if (StrUtil.isNotEmpty(fragmentQL)) {
            QlFragmentSegment qlFragmentSegment = createAndAddSegment(new QlFragmentSegment(), segmentType, (Field) null, null);
            qlFragmentSegment.setFragmentQlString(fragmentQL);
        }
        return (ChildWrapper) this;
    }

    /**
     * 设置完善的QL语句,此方法设置后无法逆推另外的QL语句(即设置的QL为SQL语句,则只能获取SQL语句).
     *
     * @param isHQL           语句是否为HQL语句
     * @param qlString        S/HQL语句字符串,如存在占位符,请务必使用Hibernate支持的中文字符
     * @param hibernateParams 相关占位符
     */
    public final ChildWrapper setCompleteQL(boolean isHQL, String qlString, Pair<String, ?>... hibernateParams) {
        if (StrUtil.isEmpty(qlString)) {
            throw new IllegalArgumentException("无效的" + (isHQL ? "H" : "S") + "QL语句设置!");
        }

        QlCompleteSegment node = new QlCompleteSegment();
        node.setIsHQL(isHQL);

        QlCompleteSegment segment = replaceOrAddSegment(node, SegmentEnum.CUSTOM_COMPLETE_SEGMENT, (Field) null, null);

        segment.setQlString(qlString);
        segment.setHibernateParams(hibernateParams);

        //后续流程补全
        this.setCompleteQL0(isHQL, qlString);
        return (ChildWrapper) this;
    }

    /**
     * 根据用户查询的自定义处理逻辑执行Lambda语句获取对应结果并返回
     *
     * @param isHql
     * @param session
     * @return
     */
    public <Response> Response executeQuery(boolean isHql, Session session) {
        if (session == null) {
            throw new LambdaCheckException("执行Query对象调用时,传入Session不能为空!");
        }
        QueryHandlerFunction queryHandlerFunc = this.userQueryHandlerFunc;
        if (queryHandlerFunc == null) {
            throw new LambdaCheckException("执行Query对象调用时,无法找到用户自定义处理执行器=[queryHandlerFunc],请使用[setUserQueryHandlerFunc]方法进行设置!");
        }
        return (Response) queryHandlerFunc.handler(isHql, this.getQuery(session, isHql), this);
    }

    /**
     * 获取Lambda表达式对应方法属性的列名对象
     *
     * @param fn
     * @return
     */
    protected final Field getColumnFieldByFn(FieldFunction fn) {
        if (fn == null) {
            return null;
        }
        SerializedLambda serializedLambda = SerializedLambdaUtil.resolve(fn);
        //获取Lambda属性对应的类型
        Class<?> fnClass = SerializedLambdaUtil.getImplClass(serializedLambda);
        if (fnClass == null) {
            throw new LambdaCheckException("无法解析Lambda表达式相关属性数据,请检查!");
        }
        if (getEntityClass() == null) {
            //回正EntityClass,如EntityClass为空,则一定为主实体类下行方法调用.
            setEntityClass((Class<MainEntity>) fnClass);
        }

        //映射属性并返回符合期望的列名属性对象.
        Field columnField = ClassUtil.getDeclaredField(fnClass, SerializedLambdaUtil.getFieldNameByLambda(serializedLambda));
        if (columnField == null) {
            //可能有些不按照标准语法进行属性名映射的数据,则需要再次尝试.
            columnField = ClassUtil.getDeclaredField(fnClass, SerializedLambdaUtil.getFieldNameByLambda(serializedLambda, true));
        }
        return columnField;
    }


    protected <ChileSegment extends Segment> ChileSegment replaceOrAddSegment(ChileSegment node, SegmentEnum segmentType, FieldFunction fn, String columnName) {
        Field columnField = getColumnFieldByFn(fn);
        return replaceOrAddSegment(node, segmentType, columnField, columnName);
    }

    /**
     * 优先执行替换语句,如不满足替换要求,则执行新增语句逻辑
     * <p>
     * 注意：接收方必须通过返回值重新接收,反正替换对象无法正确获取的问题
     *
     * @param node
     * @param segmentType
     * @param columnField
     * @param columnName
     * @param <ChileSegment>
     * @return
     */
    protected <ChileSegment extends Segment> ChileSegment replaceOrAddSegment(ChileSegment node, SegmentEnum segmentType, Field columnField, String columnName) {
        List<ChileSegment> childSegments = getSegmentsByEnum(segmentType);
        if (CollectionUtil.isEmpty(childSegments)) {
            return createAndAddSegment(node, segmentType, columnField, columnName);
        }

        for (ChileSegment oc : childSegments) {
            if (node.needReplace(oc)) {
                log.debug("本次调用SegmentNodeClass=[" + node.getClass() + "]的数据时,调用方的[" + JSON.toJSONString(node) + "]同已有语句=[" + JSON.toJSONString(oc) + "]冲突,即将舍弃调用方Node...");
                return oc;
            }
        }
        return createAndAddSegment(node, segmentType, columnField, columnName);
    }

    /**
     * 包装语句对象基础属性并保存语句对象到缓存
     *
     * @param chileSegment
     * @param segmentType
     * @param fn
     * @param columnName
     * @return
     */
    protected <ChileSegment extends Segment> ChileSegment createAndAddSegment(ChileSegment chileSegment, SegmentEnum segmentType, FieldFunction fn, String columnName) {
        return createAndAddSegment(chileSegment, segmentType, getColumnFieldByFn(fn), columnName);
    }

    /**
     * 包装语句对象基础属性并保存语句对象到缓存
     *
     * @param chileSegment
     * @param segmentType
     * @param columnField  列属性对象
     * @param columnName
     * @return
     */
    protected <ChileSegment extends Segment> ChileSegment createAndAddSegment(ChileSegment chileSegment, SegmentEnum segmentType, Field columnField, String columnName) {
        chileSegment.setSegmentType(segmentType);
        chileSegment.setColumnName(columnName);

        if (columnField != null) {
            chileSegment.setColumnEntityClass(columnField.getDeclaringClass());
            chileSegment.setColumnField(columnField);
            chileSegment.setColumnFieldName(columnField.getName());
        }
        //保存语句对象
        SEGMENT_CACHE.compute(chileSegment.getSegmentType(), (key, value) -> {
            value = value == null ? new ArrayList<>() : value;
            value.add(chileSegment);
            return value;
        });
        return chileSegment;
    }


    /**
     * 获取对应语句的Segment列表
     *
     * @param segmentTypes
     * @return
     */
    protected final <E extends Segment> Map<SegmentEnum, List<E>> getSegmentsByEnums(SegmentEnum... segmentTypes) {
        Map<SegmentEnum, List<E>> res = new HashMap<>(segmentTypes.length << 2);

        if (CollectionUtil.isEmpty(SEGMENT_CACHE)) {
            return res;
        }
        for (SegmentEnum n : segmentTypes) {
            res.put(n, (List<E>) SEGMENT_CACHE.get(n));
        }
        return res;
    }

    /**
     * 判断Segment是否存在
     *
     * @param segmentType
     * @return
     */
    protected final boolean existDataFromSegmentEnum(SegmentEnum segmentType) {
        return SEGMENT_CACHE.containsKey(segmentType);
    }

    /**
     * 根据实体类对象解析出所有数据库表的列名属性
     * <p>
     * 1.排除存在[{@link Transient}]注解的属性
     * 3.添加存在 {@link Column}注解的属性
     * 4.添加存在 {@link JoinColumn}注解的属性
     * 5.添加{get or is}添加方法中存在上述注解的属性
     *
     * @return
     */
    public List<Field> explainColumnFieldListByEntityClass() {
        return explainColumnFieldListByClass(checkAndGetValidEntityClass());
    }

    public List<Field> explainColumnFieldListByClass(Class<?> exCls) {
        Field[] allField = exCls.getDeclaredFields();

        if (ArrayUtil.isEmpty(allField)) {
            return Collections.EMPTY_LIST;
        }

        List<Field> columnFieldList = new ArrayList<>(allField.length);
        for (Field field : allField) {
            if (field.getAnnotation(Transient.class) != null) {
                //直接排除存在该注解的属性.
                continue;
            }
            if (field.getAnnotation(Column.class) != null || field.getAnnotation(JoinColumn.class) != null) {
                columnFieldList.add(field);
            }

            ////add by lx Date:2023/2/3 Desc: 尝试在get{Field}方法中寻找注解.
            Method getterMethod = CommonClassUtil.getGetterMethod(exCls, field);
            if (getterMethod != null && (getterMethod.getAnnotation(Column.class) != null || getterMethod.getAnnotation(JoinColumn.class) != null)) {
                columnFieldList.add(field);
            }
        }
        return columnFieldList;
    }

    protected Class<MainEntity> checkAndGetValidEntityClass() {
        if (entityClass == null) {
            throw new LambdaIllegalParamException("数据库表对应的主实体类对象不存在! [Main Entity Class is null]");
        }
        return getEntityClass();
    }

    /**
     * 获取完整的自定义输入语句对象
     *
     * @param isHQL
     * @return
     */
    protected QlCompleteSegment getCustomCompleteSegment(boolean isHQL) {
        List<QlCompleteSegment> QlCompleteSegments = getSegmentsByEnum(SegmentEnum.CUSTOM_COMPLETE_SEGMENT);
        if (CollectionUtil.isEmpty(QlCompleteSegments)) {
            return null;
        }
        //说明存在自定义输入的完整SQL语句.
        for (QlCompleteSegment tt : QlCompleteSegments) {
            if (isHQL == tt.getIsHQL()) {
                return tt;
            }
        }
        //说明无法找到支持映射的QL语句.
        throw new IllegalArgumentException("未设置[" + (isHQL ? "SQL" : "HQL") + "]语句的前提下,暂不支持动态转换语句类型!");
    }


    /**
     * 根据语句类型构建完整的语句数据
     *
     * @param isHQL
     * @param parentWrapper
     * @param segmentEnum
     * @return
     */
    protected final String buildSegmentQL(boolean isHQL, AbstractWrapper parentWrapper, SegmentEnum segmentEnum) {
        ConditionKeyWordEnum joinSep = segmentEnum.getSeparator();
        List<ConditionSegment> segmentNodes = this.getSegmentsByEnum(segmentEnum);

        if (CollectionUtil.isEmpty(segmentNodes)) {
            return CommonConstant.EMPTY;
        }

        StringBuilder qlBuilder = new StringBuilder(segmentNodes.size() << 5);

        String ql;
        Segment prevSegment = segmentNodes.get(0);

        if ((ql = prevSegment.convertSegmentToQL(parentWrapper, this, isHQL)) != null) {
            //拼接首个语句(保证不为空)
            qlBuilder.append(ql);
        }
        for (int i = 1, size = segmentNodes.size(); i < size; i++) {
            Segment segment = segmentNodes.get(i);
            //判断是否需要拼接分隔符.
            if (segment.needJoinSep(joinSep, prevSegment)) {
                qlBuilder.append(joinSep.getConditionGenerator().generatorCondition(parentWrapper, this, joinSep.getFormatEl(), null, isHQL));
            }
            //获取并拼接语句
            if ((ql = segment.convertSegmentToQL(parentWrapper, this, isHQL)) != null) {
                qlBuilder.append(ql);
            }
            prevSegment = segment;
        }

        return qlBuilder.toString();
    }

    /**
     * 获取拼接整合完毕的SQL语句
     *
     * @param isHQL
     * @return
     */
    public final String getCompleteQL(boolean isHQL) {
        return getCompleteQL(isHQL, null);
    }

    /**
     * 获取拼接整合完毕的SQL语句
     *
     * @param isHQL         是否为HQL语句
     * @param parentWrapper 组合父类Wrapper
     * @return
     */
    public final String getCompleteQL(boolean isHQL, AbstractWrapper parentWrapper) {
        //优先判断是否存在自定义输入的完整语句, 如存在, 则无需重新构建.
        QlCompleteSegment QLCompleteSegment = getCustomCompleteSegment(isHQL);
        if (QLCompleteSegment != null) {
            return QLCompleteSegment.getQlString();
        }

        return getCompleteQL0(isHQL, parentWrapper);
    }

    /**
     * 设置用户处理Query执行器调用函数,如使用内部执行方法[ {@link AbstractWrapper#executeQuery(boolean, Session)} ],则必须设置
     *
     * @param userQueryHandlerFunc
     * @return
     */
    public final ChildWrapper setUserQueryHandlerFunc(QueryHandlerFunction<ChildWrapper> userQueryHandlerFunc) {
        this.userQueryHandlerFunc = userQueryHandlerFunc;
        return (ChildWrapper) this;
    }

    /**
     * 向Query中设置Hibernate相关参数
     *
     * @param isHQL
     * @param query
     */
    public final void setHibernateParamByQuery(boolean isHQL, Query query) {
        //优先判断是否存在自定义输入语句对象.
        QlCompleteSegment QLCompleteSegment = getCustomCompleteSegment(isHQL);
        if (QLCompleteSegment != null) {
            //设置自定义输入语句映射的Hibernate参数
            QLCompleteSegment.setHibernateParamByQuery(isHQL, query);
            return;
        }

        setHibernateParamByQuery0(isHQL, query);
    }

    /**
     * 补全设置完整SQL的部分属性值
     *
     * @param isHQL
     * @param qlString
     */
    protected void setCompleteQL0(boolean isHQL, String qlString) {
    }

    /**
     * 设置根据用户输入条件解析出来的相关参数到Query的参数列表
     *
     * @param isHQL
     * @param query
     */
    protected abstract void setHibernateParamByQuery0(boolean isHQL, Query query);

    /**
     * 获取根据用户输入条件拼接完整的SQL字符串
     *
     * @param isHQL
     * @param parentWrapper
     * @return
     */
    protected abstract String getCompleteQL0(boolean isHQL, AbstractWrapper parentWrapper);

}
